﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using RPGXEngine.Networking;
using RPGX.Server.Network;
using RPGX.Data.Repositories;
using RPGX.Data.Factories;

namespace RPGX.Server.Game
{
    public class GameClient
    {
        #region Properties

        public IPlayerRepository PlayerRepository
        {
            get { return _playerRepository; }
            set { _playerRepository = value; }
        }
        private IPlayerRepository _playerRepository = null;

        public TcpClient TcpClient
        {
            get { return _tcpClient; }
            private set { _tcpClient = value; }
        }
        private TcpClient _tcpClient = null;

        public Thread MainThread
        {
            get { return _mainThread; }
            set { _mainThread = value; }
        }
        private Thread _mainThread = null;

        public Int64 ClientAccountId
        {
            get { return _clientAccountId; }
            set { _clientAccountId = value; }
        }
        private Int64 _clientAccountId = 0;

        public Int64 PlayerId
        {
            get { return _playerId; }
            set { _playerId = value; }
        }
        private Int64 _playerId = 0;

        public List<NetworkMessage> MessageQueue
        {
            get { return _messageQueue; }
            set { _messageQueue = value; }
        }
        private List<NetworkMessage> _messageQueue = new List<NetworkMessage>();
        private object MessageQueueLock = new object();

        public NetworkManager NetworkManager
        {
            get { return _networkManager; }
            set { _networkManager = value; }
        }
        private NetworkManager _networkManager = null;

        public bool IsRunning
        {
            get { return _isRunning; }
            set { _isRunning = value; }
        }
        private bool _isRunning = true;
        private object IsRunningLock = new object();

        #endregion

        #region Public Methods

        public GameClient(TcpClient c, NetworkManager networkManager)
        {
            if (c == null)
                throw new RPGXServerException("c");
            if (networkManager == null)
                throw new RPGXServerException("networkManager");

            this.PlayerRepository = PlayerRepositoryFactory.GetRepository();

            this.UnLoad();

            this.TcpClient = c;
            this.NetworkManager = networkManager;

            this.Load();
        }

        public void UnLoad()
        {
            if (this.MainThread != null)
            {
                this.MainThread.Abort();

                this.MainThread = null;
            }

            if (this.TcpClient != null)
            {
                this.TcpClient.Close();

                this.TcpClient = null;
            }
        }

        public void SendMessage(NetworkMessage m)
        {
            lock (this.MessageQueueLock)
            {
                this.MessageQueue.Add(m);
            }
        }

        #endregion

        #region Private Methods

        private void Load()
        {
            this.MainThread = new Thread(this.MainThreadWorker);
            this.MainThread.Start(this.TcpClient);
        }

        private void MainThreadWorker(object data)
        {
            try
            {
                TcpClient c = (TcpClient)data;

                NetworkStream stream = c.GetStream();

                MessageReader reader = new MessageReader();
                reader.ProcessMessage += new MessageReader.ProcessMessageDelegate(reader_ProcessMessage);
                reader.OnDisconnect += new MessageReader.OnDisconnectDelegate(reader_OnDisconnect);

                reader.Read(stream);

                while (true)
                {
                    this.SendMessages(stream);

                    lock (this.IsRunningLock)
                    {
                        if (!this.IsRunning)
                            break;
                    }
                }
            }
            catch (Exception)
            {
                // perhaps not so good to do a blanket disconnect regardless of the error
                this.Disconnect();
            }

            // a good idea to do this here?
            this.UnLoad();
        }

        private void reader_OnDisconnect()
        {
            this.Disconnect();
        }

        private void Connect(Int64 clientAccountId)
        {
            this.ClientAccountId = clientAccountId;

            ClientPool.AddClient(this.ClientAccountId, this);

            // send a message to the client welcoming him :)
            this.SendWelcomeToClientMessage();

            // send a message to all existing clients
            this.SendClientConnectedMessage();

            this.PlayerId = PlayerRepository.Connect(this.ClientAccountId);
        }

        private void Disconnect()
        {
            ClientPool.RemoveClient(this.ClientAccountId);

            this.SendClientDisconnectedMessage();

            lock (this.IsRunningLock)
            {
                this.IsRunning = false;
            }

            // maybe this should be after the client game loop
            // or maybe here is good...? :)
            PlayerRepository.Disconnect(this.ClientAccountId);
        }

        private void SendWelcomeToClientMessage()
        {
            NetworkMessage_ConnectToClient clientMessage = new NetworkMessage_ConnectToClient();

            var player = this.PlayerRepository.GetPlayer(this.ClientAccountId);

            clientMessage.CurrentClientCount = ClientPool.GameClients.Keys.Count;
            clientMessage.ClientAccountId = this.ClientAccountId;
            clientMessage.Handle = player.Handle;
            clientMessage.ModelIndex = player.ModelIndex;

            this.SendMessage(clientMessage);
        }

        private void SendClientConnectedMessage()
        {
            NetworkMessage_NewClientConnected m = new NetworkMessage_NewClientConnected();

            m.ClientAccountId = this.ClientAccountId;
            m.CurrentPlayerCount = ClientPool.GameClients.Keys.Count;

            this.SendMessage(m);
        }

        private void SendClientDisconnectedMessage()
        {
            NetworkMessage_ClientDisconnected m = new NetworkMessage_ClientDisconnected();

            m.ClientAccountId = this.ClientAccountId;
            m.CurrentPlayerCount = ClientPool.GameClients.Keys.Count;

            this.SendMessage(m);
        }

        private void reader_ProcessMessage(NetworkMessage message)
        {
            this.Log(String.Format("Processing Message: {0}", message.Type));

            if (message.Type == MessageTypes.ConnectToServer)
            {
                // WELCOME TO YOU TOO :)
                NetworkMessage_ConnectToServer m = message as NetworkMessage_ConnectToServer;
                if (m == null)
                    throw new RPGXServerException("m");

                this.Connect(m.ClientAccountId);
            }
        }

        private void SendMessages(NetworkStream stream)
        {
            lock (this.MessageQueueLock)
            {
                foreach (NetworkMessage m in this.MessageQueue)
                {
                    if (m.DestinationInformation.DestinationType == ClientDestinationInfomation.DestinationTypes.Server)
                    {
                        continue;
                    }
                    else if (m.DestinationInformation.DestinationType == ClientDestinationInfomation.DestinationTypes.AllClients)
                    {
                        List<GameClient> clients = ClientPool.GameClients.Values.ToList(); // this is locked, so get it quick

                        foreach (var c in clients)
                        {
                            if (c.ClientAccountId == this.ClientAccountId)
                            {
                                this.SendMessagePure(m, stream);
                            }
                            else
                            {
                                // ensure that it is not resent to all clients (like the current one)
                                m.DestinationInformation.DestinationType = ClientDestinationInfomation.DestinationTypes.Client;

                                c.SendMessage(m);
                            }
                        }
                    }
                    else if (m.DestinationInformation.DestinationType == ClientDestinationInfomation.DestinationTypes.Team)
                    {
                    }
                    else if (m.DestinationInformation.DestinationType == ClientDestinationInfomation.DestinationTypes.Client)
                    {
                        this.SendMessagePure(m, stream);
                    }
                }

                this.MessageQueue.Clear();
            }
        }

        private void SendMessagePure(NetworkMessage m, NetworkStream stream)
        {
            this.Log(String.Format("Sending Message: {0}", m.Type));

            byte[] bytes = m.GetMessageAsBytes();

            stream.Write(bytes, 0, bytes.Length);
            stream.Flush();
        }

        private void Log(string message)
        {
            this.NetworkManager.Server.WriteOutputLine(String.Format("{0}: {1}", this.ClientAccountId, message));
        }

        #endregion
    }
}
